Skip to main content

Technical Specification

Introduction

This document provides technical specifications for Marketplace Partners integrating with Candescent Digital Banking's OIDC SSO solution. It describes the architecture, endpoints, supported features, limitations, and requirements for partner-developed solutions.

Integration Model

  • Identity Provider (IdP): Candescent Digital Banking

  • Relying Party (RP): Partner application or FI application

  • Dual-Domain Architecture:

    • Authorization Endpoint: FI's branded domain (user-facing)

    • Token Endpoint: Centralized at api.candescent.com (backend only)

Audience Types

This specification supports two integration audiences:

AudienceDescriptionAuthorization Endpoint
Financial Institution (FI)Direct integration for a single institutionSingle, known FI domain
Marketplace PartnerFintech serving multiple FIsDynamic, based on originating FI

Multi-FI Partner Integration

Marketplace Partners who offer OIDC SSO to multiple Financial Institutions must handle dynamic authorization endpoint routing. Since each FI has its own branded authorization domain, partners need to:

  1. Identify the originating FI when a user initiates SSO
  2. Route to the correct authorization endpoint for that specific FI

How Partners Identify the FI

When a user accesses your partner application via SSO, you need to determine which FI they belong to. Common methods include:

MethodDescriptionExample
Query ParameterFI identifier passed in the SSO launch URLhttps://partner.com/sso?fi=acme_bank
SubdomainFI-specific subdomain on your platformacmebank.partner.com
Path ParameterFI identifier in the URL pathhttps://partner.com/fi/acme_bank/sso
Session ContextFI determined from prior user selectionUser selected FI from dropdown

Authorization Endpoint Routing

Once you identify the FI, construct the authorization endpoint URL using the FI's specific domain:

https://<FI_DOMAIN>/usr-engage-olb/beta/v1/tpv-sso-authorize

Partner Configuration Example

Maintain a mapping of FI identifiers to their authorization domains:

{
"fi_configurations": {
"acme_bank": {
"fi_id": "acme_bank",
"fi_name": "Acme Bank",
"authorization_domain": "online.acmebank.com",
"authorization_endpoint": "https://online.acmebank.com/usr-engage-olb/beta/v1/tpv-sso-authorize"
},
"first_credit_union": {
"fi_id": "first_credit_union",
"fi_name": "First Credit Union",
"authorization_domain": "banking.firstcu.org",
"authorization_endpoint": "https://banking.firstcu.org/usr-engage-olb/beta/v1/tpv-sso-authorize"
}
}
}

Partner Authorization Flow

Partner Auth Flow
tip

Store the FI identifier in the state parameter to maintain context through the OAuth flow. This allows you to associate the callback with the correct FI configuration.

Token Endpoint (Centralized)

Unlike authorization endpoints, the token endpoint is centralized for all FIs:

  • Stage: https://api.candescent.com/digitalbanking/stage/oauth2/v1/token
  • Production: https://api.candescent.com/digitalbanking/prd/oauth2/v1/token

Your partner application uses the same token endpoint regardless of which FI the user belongs to.

Token Exchange

Request

Bash
curl -X POST 'https://api.candescent.com/digitalbanking/stage/oauth2/v1/token' \
-H 'Authorization: Basic <base64(client_id:client_secret)>' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=authorization_code' \
-d 'code=<authorization_code>' \
-d 'redirect_uri=https://yourapp.com/callback'

Response

{
"access_token": "HhijWQlJCnAKpLqy9ScF6bYlnwOa",
"token_type": "Bearer",
"expires_in": 1800,
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Decoded ID Token Example

{
"iss": "https://www.digitalinsight.com",
"sub": "user_abc123",
"exp": 1735689600,
"iat": 1735689300,
"nonce": "n-0S6_WzA2Mj",
"given_name": "John",
"family_name": "Doe",
"email": "[email protected]",
"phone_number": "+15551234567",
"birthday": "1990-01-15",
"auth_time": 1735689290
}

ID Token Validation

Validate the ID token before trusting its claims:

  1. Verify signature using the JWKS file provided by your PM
  2. Check iss matches https://www.digitalinsight.com
  3. Verify exp is in the future (token not expired)
  4. Verify nonce matches the value you sent in the authorization request
  5. Extract claims for user identification and profile data

Candescent ID tokens do not include an aud (audience) claim; do not expect or require audience validation.

warning

ID tokens expire in 5 minutes. Extract and store claims immediately after validation.

Institution-users Endpoint (Optional)

While there is no standard OIDC /userinfo endpoint, Candescent provides an optional Institution-users endpoint that can be used to retrieve additional user information after authentication.

When to Use

Use this endpoint if you need additional user data beyond what's available in the ID token claims, such as:

  • Additional profile information
  • Account-related data
  • Institution-specific user details

Request

Bash
curl -X GET 'https://api.candescent.com/digitalbanking/stage/v1/institution-users/{userId}' \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json'

The userId corresponds to the sub claim from the ID token.

tip

For complete request and response details, see the Institution-users API Reference.

Key Technical Requirements

  • Supported authentication: client_secret_basic and client_secret_post

  • ID token expires in 5 minutes (extract claims immediately)

  • Access token expires in ~30 minutes

  • No public JWKS URI; JWKS files provided by your assigned Candescent Integration PM

  • No standard OIDC /userinfo endpoint; all standard user claims are included in the ID token. For additional user data, see the optional Institution-users endpoint

  • Only standard OIDC claims supported

Endpoint Architecture

Endpoint TypeDomainUser VisiblePurpose
AuthorizationFI DomainYesUser login, branding
Tokenapi.candescent.comNoBackend token exchange

Why Two Domains?

  • Authorization occurs on the FI domain for user trust and branding

  • Token endpoint is centralized for efficiency and security

OIDC Metadata File

The metadata file defines the endpoints and capabilities of the Identity Provider. It helps the client (FI) configure their OIDC client correctly and understand how to interact with our platform.

note

Replace <FI_Domain> with your institution's domain.

Sample Metadata File (Stage Environment)

{
"issuer": "https://www.digitalinsight.com",
"authorization_endpoint": "https://<FI_Domain>/usr-engage-olb/beta/v1/tpv-sso-authorize",
"token_endpoint": "https://api.candescent.com/digitalbanking/stage/oauth2/v1/token",
"jwks_uri": "",
"response_types_supported": ["code"],
"subject_types_supported": ["pairwise"],
"id_token_signing_alg_values_supported": ["RS256"],
"scopes_supported": ["openid", "profile", "email", "phone"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
"claims_supported": [
"email",
"exp",
"iat",
"iss",
"sub",
"birthday",
"preferred_username",
"given_name",
"phone_number",
"family_name"
]
}

Metadata Field Descriptions

FieldDescription
issuerThe identifier for the OIDC provider
authorization_endpointURL for user authentication (FI-branded domain)
token_endpointURL for token exchange (centralized backend)
jwks_uriURL for JSON Web Key Set (provided separately by PM)
response_types_supportedSupported OAuth 2.0 response types
subject_types_supportedHow subject identifiers are calculated
id_token_signing_alg_values_supportedAlgorithms for signing ID tokens
scopes_supportedAvailable OAuth 2.0 scopes
token_endpoint_auth_methods_supportedClient authentication methods
claims_supportedClaims available in ID tokens

Supported Features

  • Response Types: code only

  • Grant Types: authorization_code only

  • Response Modes: query, form_post

  • Scopes: openid, profile, email, phone

  • Token Authentication: client_secret_basic (HTTP Basic) and client_secret_post

  • Subject Type: pairwise (privacy-enhanced)

  • ID Token Signing: RS256 only

Security Best Practices

  • Transport Security: TLS 1.2+ for all communications

  • Parameter Validation: State (CSRF protection) and nonce (replay protection)

  • Token Security: Extract ID token claims immediately; store access token securely

  • Credential Management: Store credentials in a secrets management system; rotate every 90 days minimum

Current Limitations

  • No public JWKS URI; JWKS files provided by your Integration PM

  • Manual JWKS update required for key rotation

  • No standard OIDC /userinfo endpoint (as defined in the OpenID Connect Core spec); all standard claims are included in the ID token. For additional user data, the Institution-users endpoint can be used as an alternative

  • No custom claims; only standard OIDC claims supported

Supported Claims

ClaimDescriptionScope Required
subUnique user identifieropenid
issToken issueropenid
expExpiration timestampopenid
iatIssued at timestampopenid
nonceReplay protection valueopenid
preferred_usernameDisplay nameprofile
given_nameFirst nameprofile
family_nameLast nameprofile
birthdayDate of birth (YYYY-MM-DD)profile
emailEmail addressemail
phone_numberPhone numberphone
auth_timeAuthentication timestampopenid

Critical Timeouts

  • Authorization code: 60–120 seconds

  • ID token: 5 minutes

  • Access token: ~30 minutes

  • Session: ≤30 minutes

Local Testing with the OIDC Toolkit

Before testing with Candescent environments, you can validate your implementation locally using the OIDC Toolkit. The toolkit simulates the complete OIDC flow with the same endpoints, claims, and token format used in production.

Quick Start

Option 1: Native Setup (Recommended for Development)

Bash
# Clone the toolkit
git clone https://github.com/candescent-dev/oidc-sso-toolkit.git
cd oidc-sso-toolkit

# Start backend (Terminal 1)
cd sample-web-app/backend
npm install
npm run start:dev # Development mode with hot-reload

# Start frontend (Terminal 2)
cd sample-web-app/frontend
npm install
npm start # Starts Vite dev server on port 8000

# Open http://localhost:8000
Default Certificates

Default development certificates are included for convenience. These are safe for local testing but should not be used in production or QA environments.

Option 2: Docker (Quick Testing)

Bash
# Pull and run pre-built image
docker pull ghcr.io/candescent-dev/oidc-sso-toolkit:latest
docker run -p 8000:8000 -p 9000:9000 ghcr.io/candescent-dev/oidc-sso-toolkit:latest

# Open http://localhost:8000
Authentication

If you encounter an "unauthorized" error when pulling the image, authenticate with GitHub Container Registry:

Bash
docker login ghcr.io -u YOUR_GITHUB_USERNAME

Use a GitHub Personal Access Token (PAT) with read:packages permission.

What the Toolkit Provides

FeatureToolkitProduction
Authorization endpointlocalhost:9000/api/auth/authorizeFI-specific domain
Token endpointlocalhost:9000/api/auth/tokenapi.candescent.com
Client credentialsAuto-generatedPM-provided
JWK for validationDownloadablePM-provided
ID token claimsSame as productionSame
tip

The toolkit generates ID tokens with identical claims to production, making it ideal for testing your token parsing and validation logic.

For complete setup instructions, see the Local Development Guide.

Testing Your Credentials

Verify your credentials are correctly configured by making a test token request:

Bash
# Generate Base64 credentials
echo -n "your_client_id:your_client_secret" | base64

# Test authentication (use an invalid code to test credentials only)
curl -X POST 'https://api.candescent.com/digitalbanking/stage/oauth2/v1/token' \
-H 'Authorization: Basic <base64_credentials>' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=authorization_code&code=TEST'
ResponseMeaning
invalid_grant✅ Credentials valid, code was invalid (expected)
invalid_client❌ Credentials incorrect

Python Implementation

A complete Flask implementation for the OIDC callback handler:

import json
import secrets
import base64
import requests
from flask import Flask, redirect, request, session, url_for
from jwcrypto import jwk, jwt

app = Flask(__name__)
app.secret_key = secrets.token_hex(32)

# Configuration - replace with your values
CLIENT_ID = "your_client_id"
CLIENT_SECRET = "your_client_secret"
REDIRECT_URI = "https://yourapp.com/callback"
TOKEN_ENDPOINT = "https://api.candescent.com/digitalbanking/stage/oauth2/v1/token"
ISSUER = "https://www.digitalinsight.com"

# Load JWKS from file provided by your PM
with open("jwks.json") as f:
JWKS = jwk.JWKSet.from_json(f.read())

@app.route("/login")
def login():
"""Redirect user to FI authorization endpoint."""
state = secrets.token_urlsafe(32)
nonce = secrets.token_urlsafe(32)
session["oauth_state"] = state
session["oauth_nonce"] = nonce

# Get FI-specific authorization endpoint (from your configuration)
auth_endpoint = "https://online.acmebank.com/usr-engage-olb/beta/v1/tpv-sso-authorize"

params = {
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"response_type": "code",
"scope": "openid profile email",
"state": state,
"nonce": nonce
}
return redirect(f"{auth_endpoint}?{'&'.join(f'{k}={v}' for k, v in params.items())}")

@app.route("/callback")
def callback():
"""Handle the authorization callback."""
# Validate state
if request.args.get("state") != session.get("oauth_state"):
return "State mismatch", 400

code = request.args.get("code")
if not code:
return "Missing authorization code", 400

# Exchange code for tokens
credentials = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
response = requests.post(
TOKEN_ENDPOINT,
headers={
"Authorization": f"Basic {credentials}",
"Content-Type": "application/x-www-form-urlencoded"
},
data={
"grant_type": "authorization_code",
"code": code,
"redirect_uri": REDIRECT_URI
}
)

if not response.ok:
return f"Token exchange failed: {response.text}", 400

tokens = response.json()
id_token = tokens["id_token"]

# Validate and decode ID token
try:
token = jwt.JWT(key=JWKS, jwt=id_token)
claims = json.loads(token.claims)

# Validate claims
if claims["iss"] != ISSUER:
return "Invalid issuer", 400
if claims["nonce"] != session.get("oauth_nonce"):
return "Invalid nonce", 400

# Store user info in session
session["user"] = {
"sub": claims["sub"],
"name": f"{claims.get('given_name', '')} {claims.get('family_name', '')}",
"email": claims.get("email")
}
return redirect(url_for("dashboard"))

except Exception as e:
return f"Token validation failed: {e}", 400

@app.route("/dashboard")
def dashboard():
if "user" not in session:
return redirect(url_for("login"))
return f"Welcome, {session['user']['name']}!"

Required packages:

Bash
pip install flask requests jwcrypto

Troubleshooting & Support

  • Invalid redirect_uri: Ensure exact match and HTTPS

  • State validation failed: Store and validate state parameter

  • Authorization code expired: Exchange immediately (60–120 seconds)

  • Client authentication failed: Verify credentials encoding

  • Token signature validation fails: Ensure correct JWKS file and environment

  • JWKS key rotation: PM will notify and provide updated files

For questions or support, contact your assigned Candescent Integration PM.

References